#!/usr/local/bin/python
# -*- coding: utf-8 -*-

#
# created by Peregrinus
# licensed under GNU GPL v2
#

# TODO:
# - generated ncx head
# - more comments


from random import Random
import datetime

# CONSTANTS:

S_PUBLISHER = "KindlePeriodicalCreator"

HTML_BEGIN = """<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>"""

HTML_HEADBODY = """</title>
  </head>
  <body>
"""

HTML_END = """  </body>
</html>"""

NCX_HEAD = """<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="cs-CZ">
<head>
<meta name="dtb:uid" content="1632716" />
<meta name="dtb:depth" content="4" />
<meta name="dtb:totalPageCount" content="0" />
<meta name="dtb:maxPageNumber" content="0" />
</head>
"""

NCX_FOOT = """    </navPoint>
</navMap>
</ncx>"""

OPF_METADATA_BEGIN = """<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="uid">
<metadata>
    <dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
"""

OPF_METADATA_DCX = """    </dc-metadata>

    <x-metadata>
        <output encoding="utf-8" content-type="application/x-mobipocket-subscription-magazine" />
"""

OPF_METADATA_END = """    </x-metadata>
</metadata>
"""

S_TAB = "  "

# VARIABLES:

sectionCounter = 0;

# FUNCTIONS:

def tab(size):
    """Makes a tab indent (using variable S_TAB)"""
    
    # We need number as indent size
    if not isinstance(size, int):
        return
    
    result = ""
    while size > 0:
        result = result + S_TAB
        size = size - 1
    return result
    
class Section(object):
    """Kindle periodical - Section object definition"""
    secID = None    # section id, int
    name = ""       # name of section, string
    src = ""        # path to section file, string
    articles = []   # list of articles of this section, list of Article
    
    def __init__(self, name, secID, src, articles):
        self.name = name
        self.secID = secID
        self.src = src
        self.articles = articles
        
    def __str__(self):
        return str(self.secID) + ": " + self.name + " -> " + self.src

class Article(object):
    """Kindle periodical - Article object definition"""
    artID = None        # article id, int
    playOrder = None    # play order parameter, int
    name = ""           # name of article, string
    src = ""            # path to article file, string

    def __init__(self, artID, name, src, playOrder = None):
        self.artID = artID
        self.name = name
        self.src = src
        
        # default playOrder value is article id
        if playOrder:
            self.playOrder = playOrder
        else:
            self.playOrder = self.artID
            
    def __str__(self):
        s = str(self.artID) + " (" + str(self.playOrder) + "): "
        s = s + self.name + " -> " + self.src 
        return s

class KindlePeriodicalCreator(object):
    """Kindle Periodical Creator - main object"""
    
    # VARIABLES:
    tabSize = 0             # default indent value, int
    title = ""              # title of periodical, string
    author = ""             # author of periodical, string
    src = ""                # source of title page, string
    sections = None         # sections of periodical, list of Section
    files = None            # list of all used files, list of string
    language = None         # language of periodical, string
    uid = None              # uid of periodical, string
    date = None             # date of creation of periodical, string
    coverSrc = None         # path to cover image, string
    cssFile = None          # path to style sheet file, string

    # GENERATED:    
    tocFileSrc = None       # path to Table of Contents file, string
    opfSpineFiles = []      # files in opf spine section
    
    # CONSTANTS:
    _tocFile = "toc.ncx"        # NCX (Table of Contents) file to generate
    _opfFile = "desc.opf"       # OPF (description) file to generate
    _tocHtmlFile = "toc.html"   # HTML Table of Contents file to generate
    
    
    def __init__(self, title, author, src, sections, files, language = "en", uid = None, date = None):
        """Initialization"""
        
        self.title = title
        self.author = author
        self.src = src
        self.sections = sections
        self.files = files
        self.language = language
        
        # if date is not selected, use now
        if not date:
            now = datetime.datetime.now()
            self.date = now.strftime("%Y%m%d%H%M%S")
        # if no uid is selected, use date and random number
        if not uid:
            self.uid = self.date + "-" + str(Random.randint(Random(), 0, 65535))
        
        # set name of files depending on the title
        self._opfFile = "%(title)s.opf" % {"title" : title}
        self._tocFile = "%(title)s.ncx" % {"title" : title}
        self._tocHtmlFile = "%(title)s_toc.html" % {"title" : title}
    
    def createTocFile(self, tocTitle = "Table of Contents", sections = None):
        """Create ToC HTML file"""
        
        # if no section list is defined, use defualt one
        if not sections:
            sections = self.sections
        
        sTocFile = HTML_BEGIN
        sTocFile = sTocFile + tocTitle + HTML_HEADBODY
        sTocFile = sTocFile + tab(2) + "<h1>" + tocTitle + "</h1>\n\n"
        sTocFile = sTocFile + tab(2) + "<div>\n"
        
        # list all sections
        for section in sections:
            sTocFile = sTocFile + tab(3) + "<h3><a href=\"" + section.src + "\">" + section.name + "</a></h3>\n"
            sTocFile = sTocFile + tab(3) + "<ul>\n"
            
            # list all articles
            for article in section.articles:
                sTocFile = sTocFile + tab(4) + "<li><a href=\"" + article.src + "\">" + article.name + "</a></li>\n"
            
            sTocFile = sTocFile + tab(3) + "</ul>\n\n"
        
        sTocFile = sTocFile + tab(2) + "</div>\n"
        sTocFile = sTocFile + HTML_END
        
        # write string to file
        fTocFile = open( self._tocHtmlFile, "w" ) 
        fTocFile.write(sTocFile)
        fTocFile.close()
        
        # set newly generated file
        self.tocFileSrc = self._tocHtmlFile
        
    def setCoverSrc(self, coverSrc):
        """Set cover of periodical"""
        self.coverSrc = coverSrc
        
    def setCssFile(self, cssFileSrc):
        """Set style sheet file"""
        self.cssFile = cssFileSrc    
        
    def createFiles(self, generateToc = True):
        """Create all files for periodical"""
        
        if generateToc:
            self.createTocFile()
        
        self.createNCX()
        self.createOPF()
    
    def createNCX(self):
        """Creates NCX ToC file"""
        
        # set default indent size
        self.tabSize = 0
        
        # create ncx head part with title and author
        result = self.createNcxHead(self.title, self.author)
        # add ncx sections with articles
        for section in self.sections:
            result = result + self.createNcxSection(section)
        # add end of ncx file
        result = result + self.createNcxFoot()
        
        # write string to file
        fNCXFile = open( self._tocFile, "w" )
        fNCXFile.write(result)
        fNCXFile.close()
            
    def createNcxHead(self, title, author, tabSize = None):
        """Creates beginning of NCX file and fill in title and author"""
        
        # if indent size is not defined, use default
        if not tabSize:
            tabSize = self.tabSize
        
        result = NCX_HEAD
        result = result + tab(tabSize) + "<docTitle><text>%(title)s</text></docTitle>\n" % {"title" : title}
        result = result + tab(tabSize) + "<docAuthor><text>%(author)s</text></docAuthor>\n" % {"author" : author}
        result = result + tab(tabSize) + "<navMap>\n"
        tabSize = tabSize + 2
        result = result + tab(tabSize) + "<navPoint class=\"periodical\">\n"
        tabSize = tabSize + 2
        result = result + tab(tabSize) + "<navLabel><text>%(title)s</text></navLabel>\n" % {"title" : title}
        result = result + tab(tabSize) + "<content src=\"%(src)s\" />\n" % {"src" : self.src}
        
        if not tabSize:
            self.tabSize = tabSize
        return result + "\n"
    
    def createNcxFoot(self, tabSize = None):
        """Creates end of NCX file"""
        if not tabSize:
            tabSize = self.tabSize
        
        result = NCX_FOOT
        
        return result
    
    def createNcxArticle(self, article, tabSize = None):
        """Write articles in specified section to NCX file"""
        if not tabSize:
            tabSize = self.tabSize
        
        artString = tab(tabSize) + "<navPoint class=\"article\" id=\"%(artID)s\" playOrder=\"%(playOrder)s\">\n" \
            % {"artID" : article.artID, "playOrder" : article.playOrder}
        tabSize = tabSize + 2
        artString = artString + tab(tabSize) + "<navLabel><text>%(artName)s</text></navLabel>\n" % {"artName" : article.name}
        artString = artString + tab(tabSize) + "<content src=\"%(artSrc)s\" />\n" % {"artSrc" : article.src}
        tabSize = tabSize - 2
        artString = artString + tab(tabSize) + "</navPoint>\n"
        
        return artString
    
    def createNcxSection(self, section, tabSize = None):
        """Writes sections to NCX file"""
        
        if not tabSize:
            tabSize = self.tabSize
                    
        secStringBegin = tab(tabSize) + "<navPoint class=\"section\" id=\"section_%(secID)s\">\n" % {"secID" : section.secID}
        tabSize = tabSize + 2
        secStringBegin = secStringBegin + tab(tabSize) + "<navLabel><text>%(secName)s</text></navLabel>\n" % {"secName" : section.name}
        secStringBegin = secStringBegin + tab(tabSize) + "<content src=\"%(secSrc)s\" />\n" % {"secSrc" : section.src}
        tabSize = tabSize - 2
        secStringEnd = tab(tabSize) + "</navPoint>\n"
        
        result = secStringBegin + "\n"
        for article in section.articles:
            result = result + self.createNcxArticle(article, tabSize + 2)
        result = result + secStringEnd
        
        return result
    
    def createOPF(self):
        self.tabSize = 0
        result = self.createOpfHead(self.title, self.author, self.language, None, None, None, None, self.tabSize)
        result = result + "\n" + self.createOpfManifest(self.files)
        result = result + "\n" + self.createOpfSpine(self.opfSpineFiles)
        result = result + "\n" + self.createOpfGuide(self.sections[0].src)
        result = result + "\n</package>"
        
        fOPFFile = open( self._opfFile, "w" )
        fOPFFile.write(result)
        fOPFFile.close()
    
    def createOpfHead(self, title, author, language = "en", uid = None, date = None, \
                      publisher = None, subject = None, tabSize = None):
        if not tabSize:
            tabSize = self.tabSize
        
        if not uid:
            uid = self.uid
        if not date:
            date = self.date
        if not publisher:
            publisher = S_PUBLISHER
        if not subject:
            subject = "periodical"

        result = OPF_METADATA_BEGIN
        
        tabSize = tabSize + 4
        result = result + tab(tabSize) + "<dc:title>%(title)s</dc:title>\n" % {"title" : title}
        result = result + tab(tabSize) + "<dc:language>%(lang)s</dc:language>\n" % {"lang" : language}
        result = result + tab(tabSize) + "<dc:identifier id=\"uid\">%(uid)s</dc:identifier>\n" % {"uid" : uid}
        result = result + tab(tabSize) + "<dc:creator>%(author)s</dc:creator>\n" % {"author" : author}
        result = result + tab(tabSize) + "<dc:publisher>%(publisher)s</dc:publisher>\n" % {"publisher" : publisher}
        result = result + tab(tabSize) + "<dc:subject>%(subject)s</dc:subject>\n" % {"subject" : subject}
        result = result + tab(tabSize) + "<dc:date>%(date)s</dc:date>\n" % {"date" : date}
        result = result + tab(tabSize) + "<dc:description></dc:description>\n"
        tabSize = tabSize - 4
        
        result = result + self.createOpfXMeta()
        
        return result    
    
    def createOpfXMeta(self):
        result = OPF_METADATA_DCX
        if self.coverSrc:
            result = result + tab(4) + "<EmbeddedCover>%(coverSrc)s</EmbeddedCover>\n" % {"coverSrc" : self.coverSrc}
        result = result + OPF_METADATA_END
        
        return result
    
    def createOpfManifest(self, files, tocFile = None, cssFile = None, tabSize = None):
        if not tabSize:
            tabSize = self.tabSize
        if not tocFile:
            tocFile = self.tocFileSrc
        if not cssFile:
            cssFile = self.cssFile
            
        result = tab(tabSize) + "<manifest>\n"
        
        tabSize = tabSize + 2
        result = result + tab(tabSize) + "<item id=\"toc\" media-type=\"application/x-dtbncx+xml\" href=\"%(toc)s\" />\n" \
            % {"toc" : self._tocFile}
        
        if tocFile:
            result = result + tab(tabSize) + "<item id=\"tocFile\" media-type=\"application/xhtml+xml\" href=\"%(tocFile)s\" />\n" \
                % {"tocFile" : tocFile}
        if cssFile:
            result = result + tab(tabSize) + "<item id=\"style\" media-type=\"text/css\" href=\"%(cssFile)s\" />\n" \
                % {"cssFile" : cssFile}
        
        for i, src in enumerate(files):
            srcID = "file_" + str(i)
            self.opfSpineFiles.append(srcID)
            result = result + tab(tabSize) + "<item id=\"%(id)s\" media-type=\"application/xhtml+xml\" href=\"%(src)s\" />\n" \
                % {"id" : srcID, "src" : src}
        
        tabSize = tabSize - 2

        result = result + tab(tabSize) + "</manifest>\n"
        
        return result
    
    def createOpfSpine(self, spineFiles, tabSize = None):
        if not tabSize:
            tabSize = self.tabSize
        
        result = tab(tabSize) + "<spine toc=\"toc\">\n"
        
        tabSize = tabSize + 2
        for spineFile in spineFiles:
            result = result + tab(tabSize) + "<itemref idref=\"%(fileID)s\" />\n" % {"fileID" : spineFile}
        tabSize = tabSize - 2
        
        result = result + tab(tabSize) + "</spine>\n"
        
        return result
    
    def createOpfGuide(self, startFile = None, tocFile = None, tabSize = None):
        if not tabSize:
            tabSize = self.tabSize
        if not tocFile:
            tocFile = self.tocFileSrc
        
        result = tab(tabSize) + "<guide>\n"
        tabSize = tabSize + 2
        if startFile:
            result = result + tab(tabSize) + "<reference type=\"start\" title=\"start\" href=\"%(startFile)s\" />\n" \
                % {"startFile" : startFile}
        if tocFile:
            result = result + tab(tabSize) + "<reference type=\"toc\" title=\"Table of Contents\" href=\"%(tocFile)s\" />\n" \
                % {"tocFile" : tocFile}
            
        tabSize = tabSize - 2
        result = result + tab(tabSize) + "</guide>\n"
        
        return result